﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Buddy.BehaviorTree;
using Buddy.CommonBot;
using Buddy.Swtor;
using Buddy.Swtor.Objects;

using WingIt;

using Action = Buddy.BehaviorTree.Action;


namespace WingIt
{
    [RevisionControlId("$Id: DebugTools.cs 684 2012-08-08 10:09:07Z Chinajade $")]
    public static class DebugTools
    {
        public delegate string StringProviderDelegate(object context);

        /// <summary>
        /// <para>Emits to the log the rate at which the Behavior Tree is being pulsed.</para>
        /// <para>It is important that you call this method in _all_ significant places for an accurate measurement.
        /// For instance, if you just hook the "out of combat" PrioritySelector, all the time while in combat will not
        /// be measured and make it look like the pulse rate is lower than it should be.</para>
        /// <para>Conversely, you don't want to hook multiple parts of the behavior tree that would permit counting
        /// a single behavior tree pulse multiple times.  Such as hooking the general 'out of combat' routine, and a
        /// class-specific 'out of combat' routine.</para>
        /// <para>An excellent place to apply this hook is as the first element in the 
        /// TargetSelector.CreateBTPS_UpdateForBattlefieldConditions() PrioritySelector.  This will capture all in-combat
        /// and out-of-combat pulses of the behavior tree.</para>
        /// </summary>
        /// <param name="reportInterval"></param>
        /// <returns></returns>
        ///  3Aug2012-22:08UTC chinajade
        public static Composite CreateBTPS_DumpBehaviorTreePulseRate(CanRunDecoratorDelegate doWhen = null)
        {
            TimeSpan BehaviorTreePulseMaxHistory = TimeSpan.FromSeconds(1 * 60);
            TimeSpan ReportInterval = TimeSpan.FromSeconds(10);

            doWhen = doWhen ?? (c => true);

            return new Decorator(capturePulseRateWhen => doWhen(capturePulseRateWhen),
                new Action(behaviorTreePulse => {
                    DateTime now    = DateTime.Now;
                    int dummy = 0;

                    // Add new entry...
                    behaviorTreePulseHistogram.TryGetValue(now, out dummy);
                    behaviorTreePulseHistogram[now] = ++dummy;

                    // If time to report, do so...
                    TimeSpan elapsedSinceLastReport = now - behaviorTreePulseLastReported;
                    if (elapsedSinceLastReport > ReportInterval)
                    {
                        // Remove expired entries...
                        foreach (var expiredKey in behaviorTreePulseHistogram.Keys.Where(k => (now - k) > BehaviorTreePulseMaxHistory).ToArray())
                            { behaviorTreePulseHistogram.Remove(expiredKey); }

                        int pulseCounts = behaviorTreePulseHistogram.Where(kvp => kvp.Key > behaviorTreePulseLastReported).Sum(kvp => kvp.Value);
                        Logger.Debug("Behavior Tree Pulse rate: {0:N1} hertz", (float)pulseCounts * 1000 / (float)elapsedSinceLastReport.TotalMilliseconds);

                        behaviorTreePulseLastReported = now;
                    }
                    return RunStatus.Failure;
                }));
        }
        private static Dictionary<DateTime, int> behaviorTreePulseHistogram = new Dictionary<DateTime, int>();
        private static DateTime                  behaviorTreePulseLastReported = DateTime.MinValue;

        /// <summary>
        /// <para>Emits the buffs and debuffs curently on the TorCharacter as debug output.</para>
        /// </summary>
        /// <param name="torCharacterDelegate"></param>
        /// <returns></returns>
        public static Composite CreateBTPS_DumpBuffsDebuffs(CharacterSelectionDelegate torCharacterDelegate)
        {
            Logger.ContractRequires(() => torCharacterDelegate != null, () => "torCharacterDelegate != null");

            return (new Action(dumpBuffsDebuffsContext => {
                TorCharacter target = torCharacterDelegate(dumpBuffsDebuffsContext);

                if (target == null)
                    { return (RunStatus.Failure); }

                IEnumerable<TorEffect> buffs  = target.Buffs.ToArray();
                IEnumerable<TorEffect> debuffs  = target.Debuffs.ToArray();

                Logger.Debug("'{0}'--\n    Buffs: {1}\n    Debuffs: {2}",
                    target.LoggableIdentifier(),
                    ((buffs.Count() <= 0) ? "NONE" : "\"" + string.Join("\", \"", buffs.Select(b => b.Name).OrderBy(name => name)) + "\""),
                    ((debuffs.Count() <= 0) ? "NONE" : "\"" + string.Join("\", \"", debuffs.Select(b => b.Name).OrderBy(name => name)) + "\""));

                return (RunStatus.Failure);
                }));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        /// 23Jul2012-08:38UTC chinajade
        public static Composite CreateBTPS_DumpRelativeDifficulty_CurrentTarget()
        {
            return (new Decorator(dumpDifficultyCurrentTargetWhen => BuddyTor.Me.CurrentTarget != null,
                new Action(dumpRelativeDifficulty => {
                    string targetsInfo = string.Empty;

                    foreach (var target in TargetSelect.ViableTargetList())
                    {
                        targetsInfo += string.Format("\n    {0} = {1} {2}",
                                                    target.LoggableIdentifier(),
                                                    target.RelativeDifficulty(),
                                                    ((BuddyTor.Me.CurrentTarget == target) ? "(current target)" : ""));
                    }

                    Logger.Debug("Relative Difficulties (my difficulty = {0}):{1}", BuddyTor.Me.RelativeDifficulty(), targetsInfo);
                    return (RunStatus.Failure);
                })));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        // 23Jul2012-08:38UTC chinajade
        public static Composite CreateBTPS_DumpPartyRoles()
        {
            return (new Action(dumpPartyRoles => {
                    string membersInfo = string.Empty;

                    foreach (var member in BuddyTor.Me.PartyMembers().OrderBy(m => m.Name))
                    {
                        membersInfo += string.Format("\n    {0} = {1} (weight:{2}{3})",
                                            member.LoggableIdentifier(),
                                            member.PartyRole(),
                                            member.PartyRoleWeight(),
                                            ((member == BuddyTor.Me) ? ", Me" : ""));
                    }

                    Logger.Debug("PartyRoles:{0}", membersInfo);
                    return (RunStatus.Failure);
                }));
        }

        /// <summary>
        /// <para>DEBUG USE ONLY--don't use in production code!</para>
        /// <para>Useful for dropping 'markers' in a Lambda expression stream that has selected output.</para>
        /// <para>This method returns whatever is specified by the RETVALUE, argument.</para>
        /// <para>For example, given the expression: p =&gt; p.IsFriendly &amp;&amp; p.IsDead</para>
        /// <para>You can log the player's name that was included by adding the following:</para>
        /// <para>p =&gt; p.IsFriendly &amp;&amp; p.IsDead &amp;&amp; DebugTools.LambdaExpressionMarker("FriendlyNonDead", "Player name: {0}", p.Name)</para>
        /// <para>Notes: <list type="bullet">
        /// <item><description><para> * Use this method with discretion, used inappropately it can crash BuddyWing by emitting too much output too fast.</para></description></item>
        /// </list></para>
        /// </summary>
        /// <param name="markerName"></param>
        /// <param name="retValue"></param>
        /// <param name="message"></param>
        /// <param name="args"></param>
        /// <returns>true, always</returns>
        public static bool  ExpressionMarker(string markerName, bool retValue = true, string message = "", params object[] args)
        {
            Logger.ContractRequires(() => markerName != null, () => "markerName != null");
            Logger.ContractRequires(() => message != null, () => "message != null");

            Logger.Debug("LambdaExpressionMarker({0}): {1}", markerName, string.Format(message, args));
            return (retValue);
        }

        /// <summary>
        /// <para>DEBUG USE ONLY--don't use in production code!</para>
        /// <para>Useful for emitting output as a PrioritySelector is evaluated--this returned Composite always returns <c>RunStatus.Failure</c>.</para>
        /// <para>For example, given the expression: new PrioritySelector(new Decorator1()..., new Decorator2()...</para>
        /// <para>You can log anything you need by inserting a call to this method anywhere in the priority selector:</para>
        /// <para>new PrioritySelector(new Decorator1()..., CreateBTPS_InfoDump("BuffList:", string.Join(", ", BuddyTor.Me.Buffs)), new Decorator2()...</para>
        /// </summary>
        /// <param name="doWhen"></param>
        /// <param name="message"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public static Composite     CreateBTPS_InfoDump(CanRunDecoratorDelegate doWhen, StringProviderDelegate message)
        {
            Logger.ContractRequires(() => doWhen != null, () => "doWhen != null");
            Logger.ContractRequires(() => message != null, () => "message != null");

            string myMethodName = Logger.GetMyMethodName();

            return (new Decorator(doWhen,
                new Action(context => {
                    Logger.Debug("{0}", message(context));
                    return (RunStatus.Failure);
                    })));
        }

        /// <summary>
        /// Convenience wrapper around CreateBTPS_InfoDump(CanRunDecoratorDelegate doWhen, string message, params object[] args).
        /// </summary>
        /// <param name="message"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public static Composite     CreateBTPS_InfoDump(StringProviderDelegate message)
        {
            return (CreateBTPS_InfoDump(doWhen => true, message));
        }
    }
}
